using System;
using System.Net;
using System.Text;
using System.IO;
using System.Xml;
using System.Collections.Generic;
using GoogleReaderNotifier.ReaderAPI.Data;
using System.Reflection;
using System.Windows.Forms;
using System.Drawing;

namespace GoogleReaderNotifier.ReaderAPI
{
	/// <summary>
	/// Summary description for GoogleReader.
	/// </summary>
	public class GoogleReader
	{
		#region Private variables

		private CookieCollection _Cookies = new CookieCollection();
		private CookieContainer _cookiesContainer = new CookieContainer();
		private bool _loggedIn = false;
		private string[] _loginAuth;
		private XmlDocument xdocAll;

		#endregion

		#region Public variables

		public string LoginError = "";
		public string[] ProxyUser = new string[3];

		#endregion

		public bool LoggedIn
        {
            get { return _loggedIn; }
        }

		#region Application Code

        // https://www.google.com/reader/atom/user/-/state/com.google/reading-list // get full feed of items
        
		public string Login(string username, string password)
		{
			_loggedIn = false;
			LoginError = "";

			HttpWebRequest req = CreateRequest("https://www.google.com/accounts/ClientLogin?service=reader");

			string exc = PostLoginForm(req, String.Format("accountType=GOOGLE&Email={0}&Passwd={1}&service=reader&source=GRaiN-Notifier-" + Assembly.GetExecutingAssembly().GetName().Version, Uri.EscapeDataString(username), Uri.EscapeDataString(password)));

			if (exc.IndexOf("System.Net.WebException") != -1)
			{
				LoginError += exc + "\r\n";
				if (ProxyUser[0] == "request")
					return "ProxyAuthRequired";
				return "CONNECT_ERROR";
			}

			try
			{
				string _helper = GetResponseString(req);
                _loggedIn = (_helper.IndexOf("error", StringComparison.OrdinalIgnoreCase) == -1) && (_helper.IndexOf("auth", StringComparison.OrdinalIgnoreCase) != -1);
				if (_loggedIn == true)
				{
					_loginAuth = _helper.Split('\n');
					foreach (string st in _loginAuth)
					{
						if (st != string.Empty)
						{
							string[] coo = st.Split('=');
							if (coo[0] == "SID")
								_cookiesContainer.Add(new Cookie(coo[0], coo[1], "/", ".google.com"));
						}
					}

				}
				else
				{
					LoginError += _helper + "\r\n";
				}
			}
			catch (Exception ex)
			{
				_loggedIn = false;
				string _exc = ex.ToString();
				LoginError += "Exception in GetResponseString. \r\n" + _exc + "\r\n";
			}

			if (!_loggedIn)
				return "AUTH_ERROR";
			else
				LoginError = string.Empty;
				return string.Empty;
        }

        private delegate string LocateUnreadItemIdentifierDelegate(string identifierSource);

        private string LocateFeedIdentifier(string identifierSource)
        {
            return identifierSource.Substring(identifierSource.LastIndexOf("/http") + 1);
        }

        private string LocateTagIdentifier(string identifierSource)
        {
            return identifierSource.Substring(identifierSource.LastIndexOf("/label/") + "/label/".Length);
        }

        public bool CollectUnreadFeeds(UnreadItemCollection unreadFeeds)
        {
            System.Diagnostics.Debug.Assert(unreadFeeds != null, "unreadFeeds must be assigned.");
            
            return CollectUnreadItems(unreadFeeds, null, null);
        }
		
        public bool CollectUnreadTags(UnreadItemCollection unreadTags, List<string> tagFilterList)
        {
            System.Diagnostics.Debug.Assert(unreadTags != null, "unreadTags must be assigned.");
            
            return CollectUnreadItems(null, unreadTags, tagFilterList);
        }

        public void CollectTags(List<String> tags)
        {
            System.Diagnostics.Debug.Assert(tags != null, "tags must be assigned.");

            if (!LoggedIn)
            {
                throw new Exception("Must be logged in to collect tags");
            }

            XmlDocument xdoc;
            string identifier;

            xdoc = this.GetTagListXMLDocument();

            tags.Clear();

            foreach (XmlNode node in xdoc.SelectNodes("//object/string[contains(.,'/label/') and contains(.,'user/')]"))
            {
                identifier = LocateTagIdentifier(node.InnerText);

                tags.Add(identifier);
            }
        }

        public bool CollectUnreadItems(UnreadItemCollection unreadFeeds, UnreadItemCollection unreadTags, List<string> identifierFilterList)
        {
            bool result = true;
            //int unreadCount;
            XmlDocument xdoc;

            result = LoggedIn;
            
            if (result)
            {
                xdoc = this.GetAllUnreadCountsXMLDocument();
				xdocAll = this.GetSubscriptionListXMLDocument();

                // Collect feed information
                if (unreadFeeds != null)
                {
                    unreadFeeds.Clear();

					CollectUnreadItemsBySelectedNodes(xdoc, "//object/string[contains(.,'user/') and contains(.,'/state/com.google/reading-list')]", unreadFeeds, LocateFeedIdentifier, null);
                }


                // Collect tag information
                if (unreadTags != null)
                {
                    unreadTags.Clear();

                    CollectUnreadItemsBySelectedNodes(xdoc, "//object/string[contains(.,'/label/') and contains(.,'user/')]", unreadTags, LocateTagIdentifier, identifierFilterList);
                }
            }

            return result;
        }

        private void CollectUnreadItemsBySelectedNodes(XmlDocument xdoc, string nodeSelection, UnreadItemCollection unreadItems, LocateUnreadItemIdentifierDelegate locateIdentifierDelegate, List<string> identifierFilterList)
        {
            int unreadCount;
            UnreadItem unreadItem;
            string identifier;
			string feedName;
            bool addToList;

            foreach (XmlNode node in xdoc.SelectNodes(nodeSelection))
            {
                unreadCount = Convert.ToInt32(node.ParentNode.SelectSingleNode("number").InnerText);
                identifier = locateIdentifierDelegate(node.InnerText);
				feedName = GetFeedNameById(identifier);
                addToList = (unreadCount > 0) && ((identifierFilterList == null) || identifierFilterList.Contains(identifier));
                
                if (addToList)
                {
                    unreadItem = new UnreadItem();

                    unreadItem.Identifier = identifier;
					unreadItem.FeedName = feedName;
                    unreadItem.ArticleCount = unreadCount;

                    
                    unreadItems.Add(unreadItem);
                }
            }
        }        

        private XmlDocument GetAllUnreadCountsXMLDocument()
		{
			string url = "https://www.google.com/reader/api/0/unread-count?all=true";
            string theXml = GetResponseString(CreateRequest(url));

			XmlDocument xdoc = new XmlDocument();
			xdoc.LoadXml(theXml);

			return xdoc;
		}

		private XmlDocument GetTagListXMLDocument()
		{
			string url = "https://www.google.com/reader/api/0/tag/list";
			string theXml = GetResponseString(CreateRequest(url));

			XmlDocument xdoc = new XmlDocument();
			xdoc.LoadXml(theXml);

			return xdoc;
		}

		private XmlDocument GetSubscriptionListXMLDocument()
		{
			string url = "https://www.google.com/reader/api/0/subscription/list";
			string theXml = GetResponseString(CreateRequest(url));

			XmlDocument xdoc = new XmlDocument();
			xdoc.LoadXml(theXml);

			return xdoc;
		}

		private string GetFeedNameById(string feedId)
		{
			try
			{
				XmlNode node = xdocAll.SelectSingleNode("//string[@name='id' and .='feed/" + feedId + "']/../string[@name='title']");
				if (node != null)
					return node.InnerText;
				else
					return " - ";
			}
			catch{
				return " - ";
			}
		}

		#endregion 

		#region HTTP Functions

		public string GetResponseStrEx(String url)
		{
			string xstr = GetResponseString(CreateRequest(url));

			return xstr;
		}

		private string GetResponseString(HttpWebRequest req)
		{
			string responseString = null;
			try
			{
				HttpWebResponse res = (HttpWebResponse)req.GetResponse();
				res.Cookies = req.CookieContainer.GetCookies(req.RequestUri);
				_Cookies.Add(res.Cookies);
				
				using (StreamReader read = new StreamReader(res.GetResponseStream()))
				{
					responseString = read.ReadToEnd();
				}
				res.Close();
				//_currentHTML = responseString;
				
			}
			catch (WebException ex)
			{
				using (StreamReader read = new StreamReader(ex.Response.GetResponseStream()))
				{
					responseString = read.ReadToEnd();
				}
				//res.Close();
				string exc = ex.ToString();
				LoginError += responseString+"\r\n" +exc + "\r\n";
			}
			catch (Exception ex)
			{
				string exc = ex.ToString();
				LoginError += exc + "\r\n";
			}
			return responseString;
		}

		private string PostLoginForm(HttpWebRequest req, string p)
		{
			string exc = string.Empty;
			req.ContentType = "application/x-www-form-urlencoded";
			req.Method = "POST";
			//req.Referer = _currentURL;
			byte[] b = Encoding.UTF8.GetBytes(p);
			req.ContentLength = b.Length;
			try
			{
				using (Stream s = req.GetRequestStream())
				{
					s.Write(b, 0, b.Length);
					s.Close();
				}
			}
			catch (Exception ex)
			{
				exc = ex.ToString();
				if (ex.GetType().Name == "WebException" && exc.IndexOf("(407)") != -1 && ProxyUser[0] !="missing")
				{
					ProxyUser[0] = "request";
				}
			}
			return exc;
		}

		private HttpWebRequest CreateRequest(string url)
		{
			HttpWebRequest req = WebRequest.Create(url) as HttpWebRequest;
			//WebProxy defaultProxy = WebProxy.GetDefaultProxy();
            IWebProxy defaultProxy = HttpWebRequest.GetSystemWebProxy();
			req.Proxy = defaultProxy; // if we wanted to disable proxying, can be done by setting to null I think
			if (ProxyUser[0] == "request")
			{
				DialogResult result = GetProxyUser(ref ProxyUser[1], ref ProxyUser[2]);
				if (result == DialogResult.OK && ProxyUser[1] != "" && ProxyUser[2] != "")
				{
					ProxyUser[0] = "exist";
				}
				else
				{
					ProxyUser[0] = "missing";
				}
			}
			if (ProxyUser[0] == "exist" && ProxyUser[1] != "" && ProxyUser[2] != "")
			{
				req.Proxy.Credentials = new NetworkCredential(ProxyUser[1], ProxyUser[2]);
			}
			//req.Proxy = null;
			//req.UserAgent = _user.UserAgent;
			req.UserAgent = "GRaiN/" + Assembly.GetExecutingAssembly().GetName().Version + "(Google Reader Notifier for Windows)";
			req.Referer = "http://yoni-zaf.appspot.com/notifier?version=" + Assembly.GetExecutingAssembly().GetName().Version;
			req.CookieContainer = _cookiesContainer;
			if (_loggedIn)
				req.Headers.Add("Authorization", "GoogleLogin auth=" + _loginAuth[2].Split('=')[1]);
			//req.Referer = _currentURL; // set the referring url properly to appear as a regular browser
			//_currentURL = url; // set the current url so the next request will have the right referring url ( might not work for sub pages )
			return req;
		}

		#endregion 

		public static DialogResult GetProxyUser(ref string userName, ref string passWord)
		{
			Form form = new Form();
			Label label = new Label();
			TextBox textBoxUser = new TextBox();
			TextBox textBoxPass = new TextBox();
			Button buttonOk = new Button();
			Button buttonCancel = new Button();

			form.Text = "GRaiN";
			label.Text = "Proxy require username and password:";
			textBoxUser.Text = userName;
			textBoxPass.Text = passWord;
			textBoxPass.UseSystemPasswordChar = true;

			buttonOk.Text = "OK";
			buttonCancel.Text = "Cancel";
			buttonOk.DialogResult = DialogResult.OK;
			buttonCancel.DialogResult = DialogResult.Cancel;

			label.SetBounds(9, 20, 372, 13);
			textBoxUser.SetBounds(12, 40, 372, 20);
			textBoxPass.SetBounds(12, 65, 372, 20);
			buttonOk.SetBounds(228, 92, 75, 23);
			buttonCancel.SetBounds(309, 92, 75, 23);

			label.AutoSize = true;
			textBoxUser.Anchor = textBoxUser.Anchor | AnchorStyles.Right;
			textBoxPass.Anchor = textBoxPass.Anchor | AnchorStyles.Right;
			buttonOk.Anchor = AnchorStyles.Bottom | AnchorStyles.Right;
			buttonCancel.Anchor = AnchorStyles.Bottom | AnchorStyles.Right;

			form.ClientSize = new Size(396, 125);
			form.Controls.AddRange(new Control[] { label, textBoxUser, textBoxPass, buttonOk, buttonCancel });
			form.ClientSize = new Size(Math.Max(300, label.Right + 10), form.ClientSize.Height);
			form.FormBorderStyle = FormBorderStyle.FixedDialog;
			form.StartPosition = FormStartPosition.CenterScreen;
			form.MinimizeBox = false;
			form.MaximizeBox = false;
			form.AcceptButton = buttonOk;
			form.CancelButton = buttonCancel;

			DialogResult dialogResult = form.ShowDialog();
			userName = textBoxUser.Text;
			passWord = textBoxPass.Text;
			return dialogResult;
		}
	}
}
